/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.ainlolcat.aincalc;

import org.ainlolcat.ainscience.tools;

import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author Администратор
 */
public class LeksemsHandler {
        Cleks vchold;
        public LeksemsHandler(Cleks in){
            vchold = in;
        }

        //находит с заданной позиции лексемы разделители и их границу
        //в качестве идеи просто поиск скобочек и их четность и начало команд
        public int[] find_pos(List leksems, int start_pos)        {
            boolean find = false;
            int[] pos = new int[2];

            // find start position
            for (int i = start_pos; i < leksems.size(); i++)
            {
                if (((vchold.iscommand(leksems.get(i).toString())) || (leksems.get(i) == "(")) && (!find))
                {
                    pos[0] = i;
                    find = true;
                    break;
                }
            }
            //

            // find close position
            if (find)
            {

                int dif = 0;
                if (vchold.iscommand(leksems.get(pos[0]).toString()))
                {
                    for (int i = pos[0] + 1; i < leksems.size(); i++)
                    {
                        if (leksems.get(i) == "(") dif++;
                        if (leksems.get(i) == ")") dif--;
                        if (dif == 0) { pos[1] = i; return pos; }
                    }
                }
                else
                {
                    for (int i = pos[0]; i < leksems.size(); i++)
                    {
                        if (leksems.get(i) == "(") dif++;
                        if (leksems.get(i) == ")") dif--;
                        if (dif == 0) { pos[1] = i; return pos; }
                    }
                }
            }
            return null; // сисема прошла проверку на четность и не должна добраться до этого места, иначе ошибка в синтаксисе ф-й
            //
        }

        //вырезает из набора лексем кусок от start до end
        public ArrayList cut(List leksems, int start, int end)        {
            ArrayList res = new ArrayList();
            for (int i = start; i <= end; i++)
            {
                res.add(leksems.get(i));
            }
            return res;
        }

        // отечает за передачу набора лексем корректному обработчику. является промежуточным звеном
        public double sendto(List leksems)        {
            if (leksems.get(0).toString().compareTo("(") == 0)
                return base_act(leksems);
            if (leksems.get(0).toString().compareTo("sqrt") == 0)
                return sqrt(leksems);
            if (leksems.get(0).toString().compareTo("round") == 0)
                return round(leksems);
            if (leksems.get(0).toString().compareTo("cos") == 0)
                return cos(leksems);
            if (leksems.get(0).toString().compareTo("sin") == 0)
                return sin(leksems);
            if (leksems.get(0).toString().compareTo("tan") == 0)
                return tan(leksems);
            if (leksems.get(0).toString().compareTo("abs") == 0)
                return abs(leksems);
            if (leksems.get(0).toString().compareTo("pow") == 0)
                return pow(leksems);
            if (leksems.get(0).toString().compareTo("val") == 0)
                return val(leksems);
            if (leksems.get(0).toString().compareTo("max") == 0)
                return sort(leksems, 's');  // sumremum
            if (leksems.get(0).toString().compareTo("min") == 0)
                return sort(leksems, 'i');  //infinum
            Console.WriteLine("Error at " + leksems.get(0) + " unknown leksems. Internal error. Please restart programm ");
            return 0;
        }

        // первичный обработчик.
        public double start(List leksems)        {
            if ((leksems != null)&&(leksems.size()!=0))
            {
                //for (int i = 0;i<leksems.getColumnCount(); i++) Console.WriteLine(leksems.getData(i).toString());
                int[] buf = { 0, 0 };
                while (true)
                {
                    buf = find_pos(leksems, buf[1]);
                    if (buf != null)
                    {
                        double res = sendto(cut(leksems, buf[0], buf[1]));
                        for (int i = buf[0]; i <= buf[1]; i++)
                        {
                            leksems.remove(buf[0]);

                        }
                        leksems.add(buf[0], res);
                        buf[1] = buf[0];
                    }
                    else break;
                }
                return base_act(leksems);
            }
            else
            {
                Console.WriteLine("Error: corrupt string - dont consist any command");
                return Double.NaN;
            }
        }

        //может обрабатывать элементарные действия, остальное переправляет через sendto другим обработчикам
        public double base_act(List leksems)        {
            if (leksems.get(0).toString().compareTo("(") == 0) // удаляем скобочки, если они есть
            {
                leksems.remove(leksems.size() - 1);
                leksems.remove(0);
            }
            int[] buf = { 0, 0 };
            while (true)
            {
                buf = find_pos(leksems, buf[1]);
                if (buf != null)
                {
                    double res = sendto(cut(leksems, buf[0], buf[1]));
                    for (int i = buf[0]; i <= buf[1]; i++)
                    {
                        leksems.remove(buf[0]);

                    }
                    leksems.add(buf[0], res);
                    buf[1] = buf[0];
                }
                else break;
            }
            // perform *,/ ; +,-
            for (int i = 0; i < leksems.size(); i++)
            {
                if (leksems.get(i).toString().compareTo("*") == 0)
                {
                    String lek1 = leksems.get(i - 1).toString().replace(",", ".");
                    String lek2 = leksems.get(i + 1).toString().replace(",", ".");
                    double res = Double.parseDouble(lek1) * Double.parseDouble(lek2);
                    leksems.remove(i - 1); leksems.remove(i - 1); leksems.remove(i - 1);
                    leksems.add(i - 1, res); i--;
                }
                if (leksems.get(i).toString().compareTo("/") == 0)
                {
                    if (Double.parseDouble(leksems.get(i+1).toString()) != 0)
                    {
                        String lek1 = leksems.get(i - 1).toString().replace(",", ".");
                        String lek2 = leksems.get(i + 1).toString().replace(",", ".");
                        double res = Double.parseDouble(lek1) / Double.parseDouble(lek2);
                        leksems.remove(i - 1); leksems.remove(i - 1); leksems.remove(i - 1);
                        leksems.add(i - 1, res); i--;
                    }
                    else { Console.WriteLine("div by 0"); return Double.NaN; }
                }
            }
            for (int i = 0; i < leksems.size(); i++)
            {
                if (leksems.get(i).toString().compareTo("+") == 0)
                {
                    String lek1 = leksems.get(i - 1).toString().replace(",", ".");
                    String lek2 = leksems.get(i + 1).toString().replace(",", ".");
                    double res = Double.parseDouble(lek1) + Double.parseDouble(lek2);
                    leksems.remove(i - 1); leksems.remove(i - 1); leksems.remove(i - 1);
                    leksems.add(i - 1, res); i--;
                }
                if (leksems.get(i).toString().compareTo("-") == 0)
                {
                    String lek1 = leksems.get(i - 1).toString().replace(",", ".");
                    String lek2 = leksems.get(i + 1).toString().replace(",", ".");
                    double res = Double.parseDouble(lek1) - Double.parseDouble(lek2);
                    leksems.remove(i - 1); leksems.remove(i - 1); leksems.remove(i - 1);
                    leksems.add(i - 1, res); i--;
                }
            }
            //

            //Console.WriteLine("length " + leksems.Count);

            String output = leksems.get(0).toString().replace(",", ".");
            return Double.parseDouble(output);
        }

        public double val(List leksems){
            if (leksems.size()==5){
                String linkname = (String) leksems.get(2);
                String colId = (String) leksems.get(3);
                return tools.parseDouble(
                            vchold.entity.getLink(linkname).getValue(
                                tools.parseInteger(colId),
                                tools.parseInteger(vchold.getvar("num"))).getValue().toString()
                );
            }
            else{
                String columnID = (String) leksems.get(2);
                //TODO think more about this
                return tools.parseDouble(
                        vchold.entity.getColumnByOrder(tools.parseInteger(columnID))
                            .getValue(tools.parseInteger(vchold.getvar("num")))
                            .toString() //I use toString because under Column may be Integer or Double instance. Its slowest but stable
                        );
            }
        }

        public double sqrt(List leksems)        {

            ArrayList res = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (res.size() != 1)
            {
                Console.WriteLine("Error: sqrt(x) has one paremeter");
                return Double.NaN;
            }

            double x;
            String sx = res.get(0).toString().replace(",", ".");
            //if (Double.TryParse(sx, out x))
            try{
                x=Double.parseDouble(sx);
                if (x < 0)
                {
                    Console.WriteLine("Error: sqrt(x): parameter x < 0 ");
                    return Double.NaN;
                }
                else
                {
                    return Math.sqrt(x);
                }
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Error: sqrt(x) parameter x is corrupt : NaN");
                return Double.NaN;
            }
        }
        public double sin(List leksems)        {
            ArrayList res = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (res.size() != 1)
            {
                Console.WriteLine("Error: sin(x) has one paremeter");
                return Double.NaN;
            }

            double x;
            String sx = res.get(0).toString().replace(",", ".");
            try{//if (Double.TryParse(sx, out x))
                x = Double.parseDouble(sx);
                return Math.sin(x);
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Error: sin(x) parameter x is corrupt : NaN");
                return Double.NaN;
            }
        }
        public double cos(List leksems)        {
            ArrayList res = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (res.size() != 1)
            {
                Console.WriteLine("Error: cos(x) has one paremeter");
                return Double.NaN;
            }

            double x;
            String sx = res.get(0).toString().replace(",", ".");
            try{//if (Double.TryParse(sx, out x))
                x = Double.parseDouble(sx);
                return Math.cos(x);
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Error: cos(x) parameter x is corrupt : NaN");
                return Double.NaN;
            }
        }
        public double tan(List leksems)        {
            ArrayList res = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (res.size() != 1)
            {
                Console.WriteLine("Error: tan(x) has one paremeter");
                return Double.NaN;
            }

            double x;
            String sx = res.get(0).toString().replace(",", ".");
            try{//if (Double.TryParse(sx, out x))
                x = Double.parseDouble(sx);
                return Math.tan(x);
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Error: tan(x) parameter x is corrupt : NaN");
                return Double.NaN;
            }
        }
        public double abs(List leksems)        {
            ArrayList res = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (res.size() != 1)
            {
                Console.WriteLine("Error: abs(x) has one paremeter");
                return Double.NaN;
            }

            double x;
            String sx = res.get(0).toString().replace(",", ".");
            try{//if (Double.TryParse(sx, out x))
                x = Double.parseDouble(sx);
                return Math.abs(x);
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Error: abs(x) parameter x is corrupt : NaN");
                return Double.NaN;
            }
        }
        public double round(List leksems)        {
            ArrayList res = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (res.size() != 1)
            {
                Console.WriteLine("Error: round(x) has one paremeter");
                return Double.NaN;
            }

            double x;
            String sx = res.get(0).toString().replace(",", ".");
            try{//if (Double.TryParse(sx, out x))
                x = Double.parseDouble(sx);
                return Math.round(x);
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Error: round(x) parameter x is corrupt : NaN");
                return Double.NaN;
            }
        }
        public double sort(List leksems, char mode)        {
            ArrayList tosorting = prepare_param_list(cut(leksems, 2, leksems.size() - 2));
            if (tosorting.size() == 0)
            {
                Console.WriteLine("Error: sort : cannot getData perameters");
                return Double.NaN;
            }
            else
            {
                double[] to_sort = new double[tosorting.size()];
                for (int i = 0; i < to_sort.length; i++)
                {
                    to_sort[i] = Double.parseDouble(tosorting.get(i).toString());
                }

                // sort array
                for (int i = 0; i < to_sort.length; i++)
                    for (int j = i + 1; j < to_sort.length; j++)
                    {
                        if (to_sort[j] < to_sort[i])
                        {
                            double buffer = to_sort[i];
                            to_sort[i] = to_sort[j];
                            to_sort[j] = buffer;
                        }
                    }
                //

                if (mode == 's') return to_sort[to_sort.length - 1]; else return to_sort[0];
            }
        }
        public double pow(List leksems)        {
            ArrayList topow = prepare_param_list(cut(leksems,2,leksems.size()-2));
            if (topow.size() != 2)
            {
                Console.WriteLine("Error: pow has two paremeters");
                return Double.NaN;
            }

            double x,y;
            String sx = topow.get(0).toString().replace(",", ".");
            String sy = topow.get(1).toString().replace(",", ".");
            try{//if (Double.TryParse(sx, out x))
                x = Double.parseDouble(sx);
                y = Double.parseDouble(sy);
                return Math.pow(x, y);
            }
            catch (Exception e)//else
            {
                Console.WriteLine("Pow parameters is corrupt : NaN");
                return Double.NaN;
            }
        }

        public ArrayList prepare_param_list(List leksems)        {
            // после этих действий максимум, что останется это элементарные действия, так как все вложенные действия или функции отсеятся
            // clear all alien parameter (perform it )
            int[] buf = { 2, 2 };
            while (true)
            {
                buf = find_pos(leksems, buf[1]);
                if (buf != null)
                {
                    double res = sendto(cut(leksems, buf[0], buf[1]));
                    for (int i = buf[0]; i <= buf[1]; i++)
                    {
                        leksems.remove(buf[0]);

                    }
                    leksems.add(buf[0], res);
                    buf[1] = buf[0];
                }
                else break;
            }
            //




            // проверка на правильность окончания набора параметров (проверка на рядом стоящие запятые была ранее)
            if (leksems.get(leksems.size() - 1).toString().compareTo(",") == 0)
            {
                Console.WriteLine("Error: unexpected comma ");
                for (int i = 0; i < leksems.size(); i++)
                    Console.Write(leksems.get(i).toString());
                return null;
            }

            int startpos = 0;
            ArrayList output = new ArrayList();
            for (int i = 0; i < leksems.size(); i++)
            {
                if ((leksems.get(i).toString().compareTo(",") == 0)||(i+1== leksems.size()))
                {
                    double result = base_act(cut(leksems, startpos, i));
                    if (result != Double.NaN)
                    {
                        output.add(result);
                        startpos = i + 1;
                    }
                    else
                    {
                        Console.Write("Error: Cannot solve ");
                        for (int k = startpos; k <= i; k++)
                            Console.Write(leksems.get(k).toString());
                        Console.WriteLine(" in parameters");
                        return null;
                    }
                }
            }
            return output;


        }
}
